package com.example.common.utils;

import com.google.gson.Gson;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Properties;

/**
 * Aes加解密: 使用CBC加密模式, 填充算法使用PKCS5Padding
 */
@SuppressWarnings ("unused")
public abstract class AesUtils {
    private AesUtils() {}

    /**
     * 加密算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加密模式
     */
    private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";

    /**
     * 密钥
     */
    private static byte[] key;
    /**
     * 初始向量
     */
    private static byte[] iv;

    static {
        try {
            String cryptoPropertiesPath = "/assets/crypto.properties";
            InputStream is = AesUtils.class.getResourceAsStream(cryptoPropertiesPath);
            if (is == null) {
                throw new RuntimeException("缺省配置文件: " + cryptoPropertiesPath);
            }
            Properties properties = new Properties();
            properties.load(is);
            String k = properties.getProperty("aes.key");
            if (k == null || k.length() != 16) {
                throw new RuntimeException("密钥的长度必须为16");
            }
            String i = properties.getProperty("aes.iv");
            if (i == null || i.length() != 16) {
                throw new RuntimeException("初始向量的长度必须为16");
            }
            key = k.getBytes(StandardCharsets.UTF_8);
            iv = i.getBytes(StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * 加密数据
     * 将数据转为json格式后进行加密
     *
     * @param data 待加密的原始数据
     * @return 加密后的数据的base64编码
     */
    public static String encrypt(Object data) throws Exception {
        // 将数据转为json格式
        String json = new Gson().toJson(data);
        // 对json格式的数据进行加密
        byte[] encrypt = encrypt(json.getBytes(StandardCharsets.UTF_8));
        // 返回base64编码的密文
        return Base64.getEncoder().encodeToString(encrypt);
    }

    /**
     * 加密数据
     *
     * @param data 待加密的原始数据
     * @return 加密后的数据的base64编码
     */
    public static byte[] encrypt(byte[] data) throws Exception {
        // 生成密钥
        SecretKeySpec key = new SecretKeySpec(AesUtils.key, ALGORITHM);
        // 生成初始向量
        IvParameterSpec iv = new IvParameterSpec(AesUtils.iv);
        // 获取密码器实例
        Cipher cipher = Cipher.getInstance(CIPHER_MODE);
        // 初始化密码器: 指定模式为加密并设置密钥和初始向量
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        // 对数据进行加密
        return cipher.doFinal(data);
    }

    /**
     * 解密数据
     * 将base64编码的数据解密为目标类型
     *
     * @param encrypt 加密后的base64编码的数据
     * @param target  目标数据类型
     * @return 解密出的数据
     */
    public static <T> T decrypt(String encrypt, Class<T> target) throws Exception {
        // 将base64编码的密文解码
        byte[] e = Base64.getDecoder().decode(encrypt);
        // 对密文进行解密
        byte[] decrypt = decrypt(e);
        // 返回目标数据
        return new Gson().fromJson(new String(decrypt, StandardCharsets.UTF_8), target);
    }

    /**
     * 解密密文
     *
     * @param encrypt 密文
     * @return 解密出的数据
     */
    public static byte[] decrypt(byte[] encrypt) throws Exception {
        // 生成密钥
        SecretKeySpec key = new SecretKeySpec(AesUtils.key, ALGORITHM);
        // 生成初始向量
        IvParameterSpec iv = new IvParameterSpec(AesUtils.iv);
        // 获取密码器实例
        Cipher cipher = Cipher.getInstance(CIPHER_MODE);
        // 初始化密码器: 指定模式为解密并设置密钥和初始向量
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        // 对数据进行解密
        return cipher.doFinal(encrypt);
    }

    public static void main(String[] args) throws Exception {
        String data = "Hello AES!";
        System.out.println("原始数据: " + data);

        String encrypt = encrypt(data);
        System.out.println("加密后的数据为: " + encrypt);

        String decrypt = decrypt(encrypt, String.class);
        System.out.println("解密后的数据为: " + decrypt);
    }
}
